package ch.zhaw.skuh.turing.model.turingmaschine.rules;

import ch.zhaw.skuh.turing.model.turingmaschine.Factorial_N_ary;

public class FactorialRuleSet extends SubtractOneRuleSet {

	protected static int COUNTSTATES = Factorial_N_ary.COUNTSTATES;

	public FactorialRuleSet(Factorial_N_ary tc) {
		super(tc);
	}

	@Override
	public int setRules(int minState, int exitNormal, int exitSpecial,
			int[] tapes) {

		// *** factorial rules ***

		int[] tapesCurrent;
		int diffState = 0;

		// original tape-order
		tapesCurrent = tapes;

		// load rule-set 1st time
		// exit --> 2nd rule-set
		diffState = dynamicFactorial(minState, minState + 8, exitNormal,
				tapesCurrent);

		// result is now in t2 and original number-1 in t4
		// we could copy from t2 to t5, but ... we'll double all rules to avoid
		// this !!!

		// resort tapes
		// original was something like: int[] tapes = { 0, 1, 2, 3, 4 };
		tapesCurrent = new int[] { tapes[0], tapes[4], tapes[2], tapes[3],
				tapes[1] };

		// load rule-set 2nd time
		// exit --> 1st rule-set
		diffState = dynamicFactorial(diffState, minState, exitNormal,
				tapesCurrent);

		// loaded rule-set, return new minState
		return minState + COUNTSTATES;
	}

	// used twice to avoid copying from t2 to t5 ( funky stuff, might save some
	// 100 steps ;-) )
	private int dynamicFactorial(int minState, int exitNormal, int exitSpecial,
			int[] tapes) {
		int[] tapesCurrent;
		int diffState = minState;
		// *** subtraction rules ***

		// *** q0 + q1 rules ***
		// q0: subtract t1 from t4 by using t3 as stack
		// q1: ensure t1 is non-zero
		// start here
		tapesCurrent = new int[] { tapes[0], tapes[3], tapes[2] };

		diffState = setSubtractionRules(diffState + 0, diffState + 2,
				exitSpecial, tapesCurrent);

		// q2 of subtraction is empty
		diffState--;

		// *** q2 rules ***
		// move to end of t4 --> goto multiplication q2 aka q3
		tapesCurrent = new int[] { tapes[0], tapes[3], tapes[4] };
		addRule_gotoEnd4(diffState, diffState + 3, tapesCurrent);

		// *** multiplication rules *** (includes addition rules)
		// q3-q6, q7 is empty

		// multiplication needs all tapes
		tapesCurrent = tapes;

		// load multiplication rule-set
		RuleSet multiplication = new MultiplicationRuleSet(tc);
		diffState = multiplication.setRules(diffState + 1, diffState + 5,
				exitSpecial, tapesCurrent);

		// q4 of multiplication is empty
		diffState--;

		// *** q7 rules *** cleanup after multiplication
		// cleanup: run forwards, clear "0" in t1 and t5, move to end of t2
		addRule_cleanupMultiplication(diffState + 0, exitNormal, tapesCurrent);

		return diffState + 1;
	}

	// move to end of t4, t5 is already at end
	private void addRule_gotoEnd4(int q, int exitNormal, int[] tapes) {
		int e = exitNormal;

		String t1 = "_";
		String t5 = "_";

		for (int iT4 = 0; iT4 < tc.getRadix(); iT4++) {
			String t4 = Integer.toString(iT4, tc.getRadix());
			tc.addRulePadded(q, t1 + t4 + t5, q, t1 + t4 + t5, "110", tapes);
		}
		// end --> exit
		tc.addRulePadded(q, t1 + "__", e, "0" + "__", "-1-1-1", tapes);
	}

	// cleanup: run forwards, clear "0" in t1 and t5, move to end of t2
	private void addRule_cleanupMultiplication(int q, int exitNormal,
			int[] tapes) {
		int e = exitNormal;
		// t1 and t5 are both filled with "0" position at start
		// t2 has value, somewhere in middle
		// t3 is empty
		// t4 is at end = blank

		// "0____" --> continue
		tc.addRulePadded(q, "0____", q, "_____", "-10000", tapes);

		// t1, t2 and t4 are at same position (with the above) !!!

		for (int iT2 = 0; iT2 < tc.getRadix(); iT2++) {
			String t2 = Integer.toString(iT2, tc.getRadix());
			// "0" + t2 + "__0" --> continue
			tc.addRulePadded(q, "0" + t2 + "__0", q, "_" + t2 + "___", "11001",
					tapes);
		}
		// end found, prepare next iteration --> exit
		tc.addRulePadded(q, "_____", e, "1____", "000-10", tapes);
	}

}
